package com.benshou.bcss.common.jackson;

import com.benshou.bcss.common.j17common.Requires;
import com.benshou.bcss.common.rest.RequestHeaderContext;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import org.jetbrains.annotations.NotNull;
import org.springframework.boot.jackson.JsonComponent;

import java.io.IOException;
import java.time.Instant;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalQueries;
import java.util.Optional;

/**
 * Json 反序列化器，用于处理 {@link ZonedDateTime}
 *
 * @author <a href="mailto:liuzhenbang@benshou.com">Liu Zhenbang</a>
 */
@JsonComponent
public class ZonedDateTimeDeserializer extends JsonDeserializer<ZonedDateTime> implements ContextualDeserializer {

    /**
     * 默认时间格式化对象
     */
    private static final DateTimeFormatter DEFAULT_PARSER = new DateTimeFormatterBuilder()
            // 年-月-日：必须
            .append(DateTimeFormatter.ISO_LOCAL_DATE)
            // 时:分:秒.毫秒：非必需
            .optionalStart()
            .appendLiteral(' ')
            .append(DateTimeFormatter.ISO_LOCAL_TIME)
            .optionalEnd()
            // offset：非必须
            .optionalStart()
            .parseLenient()
            .appendOffsetId()
            .parseStrict()
            .optionalEnd()
            // zoneId：非必需
            .optionalStart()
            .appendLiteral('[')
            .parseCaseSensitive()
            .appendZoneRegionId()
            .appendLiteral(']')
            .optionalEnd()
            .toFormatter();

    /**
     * 时间格式化解析对象
     */
    @NotNull
    private DateTimeFormatter parser = DEFAULT_PARSER;

    @Override
    public ZonedDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        return switch (p.currentToken()) {
            case VALUE_NULL -> null;
            // 按字符串解析
            case VALUE_STRING -> {
                // 解析时间
                var timeText = p.getText();
                TemporalAccessor ta;
                try {
                    ta = this.parser.parse(timeText);
                } catch (Exception e) {
                    // todo 异常是否暴露给外部？以此来选择使用哪种异常
                    throw new UnsupportedOperationException();
                }

                // 日期是必须的 todo 异常是否暴露给外部？以此来选择使用哪种异常
                var localDate = Requires.notnull(
                        ta.query(TemporalQueries.localDate()),
                        IllegalArgumentException::new);

                // 时间，无则取当日0点
                var localTime = Optional.ofNullable(ta.query(TemporalQueries.localTime()))
                        .orElse(LocalTime.MIN);
                // 时区，无则取客户端时区或默认值
                var timezone = Optional.ofNullable(ta.query(TemporalQueries.zone()))
                        .orElseGet(RequestHeaderContext::getTimezoneDefaultSystem);

                yield ZonedDateTime.of(localDate, localTime, timezone);}
            // 按毫秒时间戳解析
            case VALUE_NUMBER_INT ->
                    Instant.ofEpochMilli(p.getLongValue())
                            .atZone(RequestHeaderContext.getTimezoneDefaultSystem());
            // todo 异常是否暴露给外部？以此来选择使用哪种异常
            default -> throw new UnsupportedOperationException();
        };
    }

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
        return Optional.ofNullable(property)
                // 提取 pattern 用以解析
                .map(it -> it.getAnnotation(JsonFormat.class))
                .map(format -> {
                    var pattern = format.pattern();
                    return pattern == null || pattern.isBlank() ? null: pattern;})
                .map(pattern -> {
                    var serializer = new ZonedDateTimeDeserializer();
                    serializer.parser = DateTimeFormatter.ofPattern(pattern);
                    return serializer;})
                .orElse(this);
    }
}
